﻿//Copyright Notice:  ©2009 Microsoft Corporation.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.DirectoryServices;

namespace Microsoft.InformationSecurity.CISF.Security.AuthZServices
{
    /// <summary>
    /// Specific behavior class used to retrieve all member's personnel numbers from
    /// all security groups and their descendant security groups.
    /// </summary>
    public class GetPersonDataByRelatedSGAD : IGetProperty
    {
        #region Contructor
        /// <summary>
        /// Specific behavior class used to retrieve all member's personnel numbers from
        /// all security groups and descendant security groups.
        /// </summary>
        /// <remarks>Strategy Pattern.</remarks>
        public GetPersonDataByRelatedSGAD() { }

        #endregion

        #region Methods

        #region GetProperties
        /// <summary>
        /// Specific behavior class used to retrieve all member's personnel numbers from
        /// all security groups and descendant security groups.
        /// </summary>
        /// <param name="entry">An Entry object.  The Entry object must have a 
        /// RelatedSGProperty object included.</param>
        public void GetProperties(Entry entry)
        {
            AuthZGlobals.PropertyType relateSGType = AuthZGlobals.PropertyType.RelatedSG;

            string filterFormat = "(cn={0})";
            string path = "GC://DC=corp,DC=microsoft,DC=com";
            string filter;

            //Used to limit AD search of members to only security groups and users.
            //The filter is built here but only really needed in the recursive method called below.
            //Was placed here for performance reasons.  Has no affect on AD call in this method.
            Collection<int>  sAMAccountTypes = new Collection<int>();
            sAMAccountTypes.Add((int)AuthZGlobals.sAMAccountType.SecurityGroup);
            sAMAccountTypes.Add((int)AuthZGlobals.sAMAccountType.User);

            string sAMAccountFilter = CISFLDAP.MakesAMAccountTypeQueryString(sAMAccountTypes);

            foreach(RelatedSGProperty sGProperty in entry.GetProperty(relateSGType))
            {
                filter = String.Format(filterFormat, sGProperty[0]);

                //Get all the AD directory entry of this group.
                PropertyCollection properties = CISFLDAP.GetADPropertiesForSingleEntry(path, filter);

                if (properties != null)
                    //Look up all the members of this directory entry and look for person data
                    //to add to the collection.
                    GetNextHierarchyLevel(properties, entry, sAMAccountFilter);
            }
        }

        #endregion

        #region GetNextHierarchyLevel
        /// <summary>
        /// Recurses through the group's member records and adds the users to the Property collection.
        /// </summary>
        /// <param name="properties">The collection of the directory entry's properties.</param>
        /// <param name="entry">An Entry object. </param>
        /// <param name="filter">sAMAccountTypes to filter the AD search on.</param>
        /// <remarks>
        /// Loop through the members of this group.
        /// How to determine if a member of this group is a security group or a user?  
        ///   Can start by weeding out where OU != UserAccounts?  No, all members are UserAccounts.
        ///   Have to call AD for every member.
        ///   How to make determination based on AD call?
        ///   Can pass the sAMAccountType for group.  If no record returns, it's a user?
        ///      No.  Record may be a service account and would have no way of knowing that.
        ///   Can pass the sAMAccountType for group and user.
        ///      If no record returned, can ignore.
        ///      If record is returned, check sAMAccountType.
        /// If, based on sAMAccountType, a member is a user, add the personData to the 
        ///   personData property.
        /// Otherwise, member is a security group, recurse.
        /// </remarks>
        private void GetNextHierarchyLevel(PropertyCollection properties, Entry entry, string filter)
        {
            string pathFormat = "GC://{0}";
            string memberIdx = "member";
            string displayNameIdx = "displayName";
            string personnelNumberIdx = "extensionAttribute4";
            string sAMAccountNameIdx = "sAMAccountName";
            string sAMAccountTypeIdx = "sAMAccountType";

            if (properties[memberIdx] != null)
            {
                foreach (object property in properties[memberIdx])
                {
                    string distinguishedName = property.ToString();

                    string path = String.Format(pathFormat, distinguishedName);

                    //Get the directory entry for this member record.  Filters for only the sAMAccountTypes
                    //security group and user.
                    PropertyCollection childProperties = CISFLDAP.GetADPropertiesForSingleEntry(path, filter);

                    if (childProperties != null)
                    {
                        if ((int)childProperties[sAMAccountTypeIdx].Value == (int)AuthZGlobals.sAMAccountType.User)
                        {
                            PersonDataProperty personData= new PersonDataProperty();

                            personData.Add(childProperties[displayNameIdx].Value.ToString(), true);

                            //If member is a user then add, else member is a Service Account with no 
                            //personnel number; so make up a negative number.
                            if (childProperties[personnelNumberIdx].Value != null)
                                personData.Add(childProperties[personnelNumberIdx].Value.ToString(), true);
                            else
                            {
                                int value = 0;
                                for (int i = 0; i < childProperties[sAMAccountNameIdx].Value.ToString().Length; ++i)
                                    value += Char.Parse(childProperties[sAMAccountNameIdx].Value.ToString().Substring(i, 1));
                                Random random = new Random(value);
                                value = -1 * random.Next();
                                personData.Add(value.ToString());
                            }
                            personData.Add(childProperties[sAMAccountNameIdx].Value.ToString(), true);

                            entry.Add(personData, true);
                        }
                        else
                            //RECURSE - Look up all the members of the security group just found.
                            GetNextHierarchyLevel(childProperties, entry, filter);
                    }
                }
            }
        }

        #endregion

        #endregion

    }
}
